Alumnos¶
Grupo 1:
- Grijalba
- Richard
- Ruiz
- Szekieta
Detalle¶
En este trabajo, exploraremos diversas técnicas de minería de datos para extraer valor del texto obtenido de una plataforma de monitoreo agrícola.
El trabajo diario de los asesores e ingenieros agrónomos es variado, y una de sus tareas clave es el monitoreo de plagas, malezas y enfermedades. Esta actividad es crucial para abordar los problemas de manera profesional, ya que antes de tomar decisiones clave para la producción, siempre es importante realizar un monitoreo que valida determinadas acciones sobre los lotes como puede ser la aplicacion de algun producto quimico.
Desde sus inicios, el monitoreo se ha realizado de diversas formas. Sin embargo, con la revolución digital que ha cobrado protagonismo en los últimos años, el monitoreo no se ha quedado atrás. Los asesores están adoptando cada vez más plataformas digitales para registrar todas las acciones realizadas. No obstante, algunas "viejas costumbres" persisten. Es común ver a asesores tomando notas de monitoreo a través de audios y notas escritas, como solían hacerlo antes en una libreta.
De esta manera, el monitoreo aún no ha alcanzado una digitalización plena. Muchos de esos audios y notas se pierden, sin aportar información valiosa para la campaña en curso y mucho menos para campañas futuras, sin mencionar la perdida de trazabilidad y valor que estas pordian aportar, aqui estamos hablando directamente de dinero que se esta perdiendo por no registrar de manera correcta los monitoreos.
El dataset se denominará annotations.csv y contendrá las siguientes columnas:
- annotation_id: ID único de la anotación
- company_id: ID de la compañía asociada a la anotación
- season_id: ID de la campaña asociada a la anotación
- property_id: ID de la propiedad asociada a la anotación
- area_id: ID del lote asociado a la anotación
- comments: Anotación en formato texto
- scouter_id: ID del asesor
- annotation_date: Fecha de la anotación
- annotation_tags: Etiquetas o tags indicados por el asesor
- annotation_longitude: Longitud de la anotación
- annotation_latitude: Latitud de la anotación
Por ejemplo, al texto comments="este lote tiene rama negra", podríamos etiquetarlo como "Maleza" o "Rama Negra". Esta nueva etiqueta aporta mucho valor a la nota, permitiendo cuantificar a nivel regional los niveles de malezas, plagas y enfermedades, y posibilitando la integración de esta información con otras fuentes de datos para aportar valor al mercado.
Librerías utilizadas¶
#!python -m spacy download es_core_news_sm
import pandas as pd
import matplotlib.pyplot as plt
import re
from unidecode import unidecode
from wordcloud import WordCloud
pd.set_option('display.max_columns', None)
#pd.set_option('display.max_rows', None)
# Podemos ejecutar la notebook con una muestra aleatoria de 1000 comentarios
# Si es True la notebook es ejecutada con la muestra de comentarios
# Si es False la notebook es ejecutada con todos los comentarios
RUN_WITH_SAMPLE = False
# Definir si vamos a guardar o no las salidas de la notebook
SAVE_NOTEBOOK_RESULTS = False
# Nombre del detaset etiquetado a partir de los comentarios
file_with_tags = "Workflow_output/annotations_processed.csv"
Lectura del dataset¶
# Importamos archivo csv
annotations = pd.read_csv('../data/annotations.csv')
annotations.head(5)
| annotation_id | company_id | season_id | property_id | area_id | comments | scouter_id | annotation_date | annotation_tags | annotation_longitude | annotation_latitude | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 01ff3c51-d6c2-4b9a-9876-409e75a824d5 | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | 289c09d7-ce26-4fa9-bf9a-b7cab391898f | bd3e8b1b-6914-4e22-9e63-1c0c66ccb148 | 91420df8-61ba-11ed-9c62-e1a227e78cde | Ir hablado con desyuyadores | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-07 13:31:09.234 | NaN | -62.578112 | -33.103599 |
| 1 | 404e3e04-b713-493e-830a-56570f997a14 | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | 036afeae-1c42-4c93-9a06-2f20977b89ac | Maiz recien sembrado . Todo ok | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 10:30:55.251 | NaN | -62.809891 | -33.741697 |
| 2 | 60f06b01-c926-42b3-b79d-5b4b61e2e6fb | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | b24c5fd5-87b2-40dd-97cc-ca4edff7a625 | Comienzo siembra maiz tardio. Excelente barbecho | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 10:30:05.531 | NaN | -62.809891 | -33.741697 |
| 3 | b53c4cac-1a21-45fe-be7c-235b08d704c9 | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | 1af5d0f7-7a22-4aef-8c10-c65c12708ab3 | Hacer preemergente con glifo y acuron | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 12:18:16.170 | NaN | -62.770486 | -33.655338 |
| 4 | 6bbdc37c-4ca4-4082-a443-aedd206cf24e | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | 9d6caf86-eeca-4124-a2fb-42394a148771 | Hacer preemergente con acuron y heat | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 12:17:43.856 | NaN | -62.770486 | -33.655338 |
Validaciones¶
# volumen de comentarios
num_annotations = annotations.shape[0]
null_count = annotations['comments'].isnull().sum()
non_null_count = annotations['comments'].notnull().sum()
print("Cantidad de comentarios:", num_annotations)
print("Comentarios 'Nulos':", null_count)
print("Comentarios 'No nulos':", non_null_count)
Cantidad de comentarios: 26930 Comentarios 'Nulos': 6374 Comentarios 'No nulos': 20556
# distribución de las etiquetas asignadas a los comentarios
annotations['annotation_tags'].value_counts(dropna=False,normalize=True)
annotation_tags NaN 0.903639 tags_AR_Resumen 0.030746 tags_general_weeds 0.023468 tags_AR_Resumen,tags_general_weeds 0.014519 tags_general_other 0.010843 tags_general_nutrient 0.003305 tags_AR_Rendimiento 0.002562 tags_general_pest 0.002525 tags_general_disease 0.002488 tags_AR_Resumen,tags_general_pest 0.001225 tags_general_deficiency 0.000706 tags_AR_Resumen,tags_general_other 0.000594 tags_general_disease,tags_general_weeds 0.000483 tags_AR_Resumen,tags_general_pest,tags_general_weeds 0.000371 [,] 0.000297 tags_general_pest,tags_general_weeds 0.000297 tags_controle_certo_application 0.000260 tags_AR_Resumen,tags_general_nutrient 0.000223 tags_general_other,tags_general_weeds 0.000186 tags_general_irrigation 0.000186 tags_AR_Resumen,tags_general_disease 0.000111 tags_controle_certo_application,tags_general_weeds 0.000111 tags_AR_Rendimiento,tags_AR_Resumen 0.000074 tags_general_deficiency,tags_general_disease,tags_general_weeds 0.000074 tags_general_deficiency,tags_general_nutrient 0.000074 tags_general_disease,tags_general_other 0.000074 tags_controle_certo_application,tags_general_deficiency 0.000037 tags_AR_Rendimiento,tags_general_other 0.000037 tags_general_nutrient,tags_general_weeds 0.000037 tags_general_other,tags_general_pest,tags_general_weeds 0.000037 tags_general_disease,tags_general_nutrient,tags_general_other 0.000037 tags_AR_Resumen,tags_general_other,tags_general_weeds 0.000037 tags_AR_Rendimiento,tags_general_weeds 0.000037 tags_general_deficiency,tags_general_disease,tags_general_nutrient 0.000037 tags_general_irrigation,tags_general_weeds 0.000037 tags_general_deficiency,tags_general_disease,tags_general_nutrient,tags_general_pest 0.000037 tags_general_deficiency,tags_general_disease 0.000037 tags_AR_Resumen,tags_general_deficiency 0.000037 tags_general_deficiency,tags_general_disease,tags_general_other,tags_general_pest 0.000037 tags_general_disease,tags_general_irrigation,tags_general_nutrient,tags_general_other,tags_general_weeds 0.000037 tags_general_disease,tags_general_pest 0.000037 Name: proportion, dtype: float64
La mayoría de los comentarios no tienen asignada una etiqueta (representa el 90% de la base).
Este campo podría ser imputado mediante el campo de comentario ('comments').
La etiqueta con mayor volumen es 'tags_general_weeds' (maleza).
# Exploración de comentarios
# Ejemplo de algunos comentarios que fueron asignados a la etiqueta 'maleza' y contienen el sstring 'no'
condicion = (annotations['annotation_tags']=='tags_general_weeds') & (annotations['comments'].str.contains('no'))
annotations[condicion].head()
| annotation_id | company_id | season_id | property_id | area_id | comments | scouter_id | annotation_date | annotation_tags | annotation_longitude | annotation_latitude | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 6740 | cde0f3a4-fc60-4e23-b0eb-dd5cc23e18bf | f203a888-9d2a-4a07-a8f5-7d5da9e27aac | 7722b6a9-2215-4362-9a79-f47f4e73b83c | 8cde247e-5b7d-4c21-8cea-c803352ac63d | 004f4288-ab55-4f08-bafd-8147d30fb1ee | 28.04.2023 lote cercano a aplicación para barb... | ebb3b3f8-d91b-4efa-b838-cca6ea83e360 | 2023-04-28 11:48:16.468 | tags_general_weeds | -63.193558 | -32.786015 |
| 7186 | 8dbc4ae1-19d2-4113-aaa2-7245344ae16b | 6fda311b-bec7-4368-8a57-99f894a2c614 | e4183986-0517-4119-bc55-97d62855e25b | 4a6a1679-ba40-48ba-8616-084d7c8a423d | b11a4a51-7f24-4e73-8c7b-a11654a287ae | Rodal de ocucha que no controlo bien | 714f8437-b26d-40ef-a441-32f77ade2a3e | 2023-06-14 14:25:25.000 | tags_general_weeds | -63.926227 | -32.800571 |
| 7255 | 178b1c3f-df3d-40f7-9987-e2655a189475 | e8b4d4a7-825e-49e4-94c5-5730fa5ad33e | 7b81b4ee-957a-4939-b7d0-e87d31f4fbda | afa3e6e5-af63-4c40-b341-efe6ce394787 | 482466a5-090f-417d-890f-69e5edadd97f | un poco de rye grass y un poco de nabo, no mucho | 54cd9c8c-0799-449c-9a22-d9f75c5dd173 | 2023-06-13 10:43:10.000 | tags_general_weeds | -61.398361 | -38.738875 |
| 7256 | e0f4b4bb-41db-44d3-900f-9d4becf93d06 | e8b4d4a7-825e-49e4-94c5-5730fa5ad33e | db99aee4-7ae9-42d7-a4b5-31d2abc81e86 | 8afecdb7-d5c5-40fc-872f-b77c10810709 | b356cf5a-d1bb-4218-9eea-25a23e383850 | Rye grass afectado pero todavía no está seco | 54cd9c8c-0799-449c-9a22-d9f75c5dd173 | 2023-06-14 14:13:30.000 | tags_general_weeds | -61.016213 | -38.803795 |
| 7276 | 4ddda2c1-8754-4f14-a20c-12e330e4136d | 33f2dcae-049a-4b93-83f3-a8014ea7f0a0 | 642ff575-39ee-4f7e-b4bc-8f4080f417b7 | 0f806757-e61d-4f67-81d0-0e71d97cb668 | 08867a7a-d869-11ed-98c6-150405b698ac | Manos grandes | a076a950-0b62-4771-bf27-c952163fab2f | 2023-06-22 12:24:42.000 | tags_general_weeds | -59.098272 | -37.575422 |
Procesamiento de los comentarios - Limpieza del dataset¶
# Extraemos una muestra de la basepara agilizar ejecución de la notebook
# Posteriormente tomaremos la base completa
annotations_full = annotations.copy()
# Tomamos muestra aleatoria de 1000 comentarios para testear su ejecución
if RUN_WITH_SAMPLE:
annotations = annotations.sample(1000, random_state=42)
# Eliminamos comentarios nulos
annotations = annotations.dropna(subset=['comments'])
Primera limpieza¶
Realizamos una primera limpieza de los comentarios:
- Llevar texto a minúscula.
- Eliminar tíldes y caracteres especiales.
- Eliminar espacios en blanco al inicio y fin del comentario.
- Eliminar cualquier carácter que no sea letra, número, espacio o /.
- Elimina múltiples espacios en blanco.
# Definimos función para limpieza de texto
def limpiar_texto(texto):
# Convierte el texto a minúsculas
texto = str(texto)
texto = texto.lower()
# Elimina tildes y caracteres especiales
texto = unidecode(texto)
# Elimina espacios en blanco al inicio y al final
texto = texto.strip()
# Elimina signos de puntuación y otros caracteres no deseados
texto = re.sub(r'[^a-zA-Z0-9\s/]', '', texto)
# Elimina múltiples espacios en blanco y los convierte en uno solo
texto = re.sub(r'\s+', ' ', texto)
# Devuelve el texto filtrado
return texto
# Aplicamos limpieza del texto - No pisamos campo original sino que creamos uno nuevo
annotations['comments_clean'] = annotations['comments'].apply(limpiar_texto)
# Comparamos el comentario original con el 'limpio'
annotations[['comments', 'comments_clean']].sample(10, random_state=42)
| comments | comments_clean | |
|---|---|---|
| 8947 | altamisa | altamisa |
| 26674 | AU | au |
| 12078 | Este lote esta siendo pastoreado 17-8 | este lote esta siendo pastoreado 178 |
| 21666 | Si bien hay sectores impactados con menor desa... | si bien hay sectores impactados con menor desa... |
| 292 | Lote limpio. En cabeceras Digitaria aislada y ... | lote limpio en cabeceras digitaria aislada y a... |
| 13008 | trigo grano pastoso, mucho grano vano | trigo grano pastoso mucho grano vano |
| 25035 | Lote en etapa de implantación del cultivo, eme... | lote en etapa de implantacion del cultivo emer... |
| 23389 | Buena apariencia general del cultivo. El trata... | buena apariencia general del cultivo el tratam... |
| 9679 | un bajo con parietaria | un bajo con parietaria |
| 11077 | El bajo hay que hacerlo con Glufosinato por Ce... | el bajo hay que hacerlo con glufosinato por ce... |
# Cantidad de caracteres de los comentarios limpios
annotations["len_comments_clean"] = annotations["comments_clean"].str.len()
# Check comportamiento
annotations["len_comments_clean"].value_counts(dropna=False)
len_comments_clean
7 738
0 479
18 430
22 359
17 334
...
851 1
797 1
957 1
383 1
412 1
Name: count, Length: 444, dtype: int64
Luego de una primera limpieza, podemos identificar comentarios vacíos.
Eliminamos estos comentarios.
# Limpiamos comentarios sin caracteres
annotations = annotations[annotations["len_comments_clean"]>0].reset_index(drop=True).copy()
Segunda limpieza¶
Además de realizar la 'primera limpieza' mencionada anteriormente, se lleva a cabo una eliminación adicional de 'stopwords' para mejorar la calidad del texto. Esto ayuda a reducir ruido en los datos al eliminar palabras comunes y sin valor predictivo, como 'de', 'la', y 'el', optimizando así el contenido para su análisis.
# TODO: mover importacion de librerias
import re
from unidecode import unidecode
import spacy
import nltk
from nltk.corpus import stopwords
# Cargar modelo en español de spaCy
nlp = spacy.load('es_core_news_sm')
# Descargar stop words de nltk en español
nltk.download('stopwords')
# Crear lista personalizada de stop words
custom_stop_words = set(unidecode(texto) for texto in stopwords.words('spanish')) - {'no', 'sin', 'con', 'pero', 'hay'}
# Tokenizar y eliminar stop words personalizadas usando spaCy
def limpiar_texto(texto):
# Convierte el texto a minúsculas
texto = str(texto).lower()
# Elimina tildes y caracteres especiales
texto = unidecode(texto)
# Elimina espacios en blanco al inicio y al final
texto = texto.strip()
# Elimina signos de puntuación y otros caracteres no deseados
texto = re.sub(r'[^a-zA-Z0-9\s/]', '', texto)
# Elimina múltiples espacios en blanco y los convierte en uno solo
texto = re.sub(r'\s+', ' ', texto)
# Procesar el texto con spaCy
doc = nlp(texto)
# Filtrar palabras irrelevantes (stop words) y solo conservar las alfabéticas
palabras_filtradas = [token.text for token in doc if token.text not in custom_stop_words and token.is_alpha]
# Devuelve el texto filtrado
return ' '.join(palabras_filtradas)
# Aplicar la función limpiar_texto a la columna 'comments' y crear la columna 'comments_clean'
annotations['comments_clean_2'] = annotations['comments'].apply(limpiar_texto)
# Cantidad de caracteres de los comentarios limpios 2
annotations["len_comments_clean_2"] = annotations["comments_clean_2"].str.len()
# Limpiamos comentarios sin caracteres
annotations = annotations[annotations["len_comments_clean_2"]>0].reset_index(drop=True).copy()
annotations[['comments', 'comments_clean','comments_clean_2']].sample(10)
| comments | comments_clean | comments_clean_2 | |
|---|---|---|---|
| 12989 | soja wacha | soja wacha | soja wacha |
| 14981 | a pesar del lote de girasol más apedrado, lleg... | a pesar del lote de girasol mas apedrado llego... | pesar lote girasol apedrado llego buen iaf cri... |
| 1003 | Cultivo | cultivo | cultivo |
| 11952 | Cultivo limpio.\r\neleusine y algo de colorado... | cultivo limpio eleusine y algo de colorado en ... | cultivo limpio eleusine colorado cabeceras pre... |
| 7373 | nabos controlados, cebada controlada, algunos ... | nabos controlados cebada controlada algunos ra... | nabos controlados cebada controlada raigras ce... |
| 3092 | Estado general muy bueno.\r\nLote limpio con p... | estado general muy bueno lote limpio con plant... | general bueno lote limpio con plantas maiz gua... |
| 17117 | Buen estado en general | buen estado en general | buen general |
| 16294 | Daño de arañuela, no hay insectos vivos. | dano de aranuela no hay insectos vivos | dano aranuela no hay insectos vivos |
| 15041 | Lote de buena apariencia, con nacimientos de o... | lote de buena apariencia con nacimientos de or... | lote buena apariencia con nacimientos orugas t... |
| 9588 | 36,6 grano/espiga | 366 grano/espiga | grano espiga |
Exploración del texto¶
Nube de palabras de los comentarios 'limpios' (con stopwords)¶
# A partir de los comentarios de la 'primera limpieza'
# Crear el objeto WordCloud
all_comments = ' '.join(annotations['comments_clean'])
wordcloud = WordCloud(width=800, height=400, background_color='white').generate(all_comments)
# Mostrar nube de palabras
plt.figure(figsize=(10, 6))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
Nube de palabras de los comentarios 'limpios' (sin stopwords)¶
# A partir de los comentarios de la 'segunda limpieza' (sin stopwords)
# Crear el objeto WordCloud
all_comments = ' '.join(annotations['comments_clean_2'])
wordcloud = WordCloud(width=800, height=400, background_color='white').generate(all_comments)
# Mostrar nube de palabras
plt.figure(figsize=(10, 6))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
Generar bigramas (pares de palabras) y trigramas (tripletas de palabras)¶
from nltk.util import ngrams
from nltk import FreqDist
# Tokenizar y filtrar palabras irrelevantes usando spaCy
def tokenize(text):
doc = nlp(text)
return [token.text for token in doc]
comments = annotations['comments_clean_2'].tolist()
filtered_words = []
for comment in comments:
words = tokenize(comment) # tokenizar
filtered_words.extend(words)
# Generar bigramas y trigramas
bigrams = ngrams(filtered_words, 2)
trigrams = ngrams(filtered_words, 3)
# Contar frecuencia de bigramas y trigramas
bigram_freq = FreqDist(bigrams)
trigram_freq = FreqDist(trigrams)
Nube de palabras a partir de los bigramas y trigramas¶
# Crear un diccionario de palabras compuestas con sus frecuencias
composite_words = {f"{k[0]} {k[1]}": v for k, v in bigram_freq.items()}
composite_words.update({f"{k[0]} {k[1]} {k[2]}": v for k, v in trigram_freq.items()})
# Crear una nube de palabras
wordcloud = WordCloud(width=800, height=400, background_color='white').generate_from_frequencies(composite_words)
# Mostrar la nube de palabras
plt.figure(figsize=(10, 5))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()
Tabla frecuencia de los bigramas¶
# Convertir bigram_freq a un DataFrame de pandas
bigram_data = pd.DataFrame(bigram_freq.items(), columns=['Bigram', 'Frequency'])
# Expandir los bigramas en dos columnas separadas
bigram_data[['Bigram1', 'Bigram2']] = pd.DataFrame(bigram_data['Bigram'].tolist(), index=bigram_data.index)
# Eliminar la columna original del bigrama
bigram_data = bigram_data.drop(columns=['Bigram'])
# Mostrar top 15
bigram_data.sort_values("Frequency", ascending=False).head(15)
| Frequency | Bigram1 | Bigram2 | |
|---|---|---|---|
| 13615 | 1194 | sin | inconvenientes |
| 13650 | 944 | buena | apariencia |
| 350 | 857 | cultivo | con |
| 87 | 700 | rama | negra |
| 136 | 656 | lote | limpio |
| 14780 | 557 | inconvenientes | adversidades |
| 64 | 450 | maiz | guacho |
| 109 | 436 | buen | control |
| 1156 | 420 | con | buena |
| 15597 | 402 | apariencia | general |
| 2312 | 357 | lote | buena |
| 357 | 352 | lote | con |
| 1680 | 348 | buen | general |
| 760 | 341 | general | cultivo |
| 519 | 318 | yuyo | colorado |
Tabla frecuencia de los trigramas¶
# Convertir trigram_freq a un DataFrame de pandas
trigram_data = pd.DataFrame(trigram_freq.items(), columns=['Trigram', 'Frequency'])
# Expandir los trigramas en tres columnas separadas
trigram_data[['Bigram1', 'Bigram2', 'Bigram3']] = pd.DataFrame(trigram_data['Trigram'].tolist(), index=trigram_data.index)
# Eliminar la columna original del trigrama
trigram_data = trigram_data.drop(columns=['Trigram'])
# Mostrar top 15
trigram_data.sort_values("Frequency", ascending=False).head(15)
| Frequency | Bigram1 | Bigram2 | Bigram3 | |
|---|---|---|---|---|
| 22593 | 544 | sin | inconvenientes | adversidades |
| 37677 | 379 | con | buena | apariencia |
| 1593 | 343 | cultivo | con | buena |
| 35427 | 340 | lote | buena | apariencia |
| 23804 | 325 | buena | apariencia | general |
| 28739 | 271 | inconvenientes | adversidades | cultivo |
| 13741 | 247 | buen | general | cultivo |
| 392 | 236 | cultivo | con | buen |
| 46879 | 188 | adversidades | cultivo | con |
| 30508 | 171 | lote | sin | inconvenientes |
| 49416 | 153 | cultivo | con | apariencia |
| 48042 | 129 | con | buen | aspecto |
| 37675 | 128 | sin | inconvenientes | cultivo |
| 36650 | 122 | buena | apariencia | con |
| 6303 | 118 | cultivo | cultivo | cultivo |
Generar diccionario para imputar 'tag' del comentario¶
# Diccionario indicadores
indicadores = pd.read_csv('../data/id_indicadores.csv')
selected_columns = ['phenomenon_name', 'phenomenon_scientific_name', 'phenomenon_category_name']
indicadores = indicadores[selected_columns]
# Primeros registros
indicadores.head()
| phenomenon_name | phenomenon_scientific_name | phenomenon_category_name | |
|---|---|---|---|
| 0 | Flor de la noche | Oenothera indecora | Malezas |
| 1 | Mancha purpura | Cercospora kikuchii | Enfermedades |
| 2 | Chilca | Baccharis salicifolia | Malezas |
| 3 | Ácaros (Araña roja) | Tetranychus urticae | Plagas |
| 4 | Boevaria | Boerhavia difussa | Malezas |
# Diccionario productos
productos = pd.read_excel('../data/id_productos.xlsx')
selected_columns = ['PRODUCTO', 'PRINCIPIO_ACTIVO', 'GRUPO']
productos = productos[selected_columns]
# Primeros registros
productos.head()
| PRODUCTO | PRINCIPIO_ACTIVO | GRUPO | |
|---|---|---|---|
| 0 | AMISTAR | AZOXISTROBINA | FUNGICIDA |
| 1 | AMISTAR 50 WG | AZOXISTROBINA | FUNGICIDA |
| 2 | AMISTAR 50 WG | AZOXISTROBINA | FUNGICIDA |
| 3 | QUADRIS | AZOXISTROBINA | FUNGICIDA |
| 4 | QUADRIS 50 WG | AZOXISTROBINA | FUNGICIDA |
# Aplicamos la misma limpieza a los diccionarios como hicimos con los comentarios
indicadores['phenomenon_name'] = indicadores['phenomenon_name'].apply(limpiar_texto)
indicadores['phenomenon_category_name'] = indicadores['phenomenon_category_name'].apply(limpiar_texto)
indicadores['phenomenon_scientific_name'] = indicadores['phenomenon_scientific_name'].apply(limpiar_texto)
productos['PRODUCTO'] = productos['PRODUCTO'].apply(limpiar_texto)
productos['PRINCIPIO_ACTIVO'] = productos['PRINCIPIO_ACTIVO'].apply(limpiar_texto)
productos['GRUPO'] = productos['GRUPO'].apply(limpiar_texto)
# Crear un nuevo DataFrame con las columnas 'phrase' y 'tag' a partir de los DataFrames 'indicadores' y 'productos'
df_indicadores = pd.DataFrame({
'phrase': pd.concat([indicadores['phenomenon_name'], indicadores['phenomenon_scientific_name']]),
'tag': pd.concat([indicadores['phenomenon_category_name'], indicadores['phenomenon_category_name']])
})
df_productos = pd.DataFrame({
'phrase': pd.concat([productos['PRODUCTO'], productos['PRINCIPIO_ACTIVO']]),
'tag': pd.concat([productos['GRUPO'], productos['GRUPO']])
})
# Unir ambos DataFrames
df_combined = pd.concat([df_indicadores, df_productos], ignore_index=True)
# Mostrar el nuevo DataFrame
df_combined.head()
| phrase | tag | |
|---|---|---|
| 0 | flor noche | malezas |
| 1 | mancha purpura | enfermedades |
| 2 | chilca | malezas |
| 3 | acaros arana roja | plagas |
| 4 | boevaria | malezas |
df_combined[df_combined['phrase'].str.contains('maleza')]
| phrase | tag |
|---|
# incluimos el tag como phrase
# Duplicar las filas y cambiar los valores en la columna 'phrase' en las filas duplicadas
df_duplicado = df_combined.copy()
df_duplicado['phrase'] = df_duplicado['tag']
df_duplicado = df_duplicado.drop_duplicates().reset_index(drop=True)
# Concatenar el DataFrame original con el duplicado
df_combined = pd.concat([df_combined, df_duplicado], ignore_index=True)
df_combined[df_combined['phrase'].str.contains('maleza')]
| phrase | tag | |
|---|---|---|
| 18164 | malezas | malezas |
| 18174 | coberturas surcos malezas | coberturas surcos malezas |
df_combined["tag"].value_counts()
tag herbicida 6009 malezas 5837 fungicida 2365 insecticida 1605 calidad siembra 763 enfermedades 243 plagas 235 herbicidadefoliantedesecante 175 plantas daninhas 161 fungicida insecticida 149 acaricidainsecticida 115 cosecha 73 insecticidaacaricidagorgojicida 59 analisis suelo 49 unkrauter 41 broad leaved weeds 41 malas hierbas 41 stand plantas 37 insecticida nematicida 35 stand 31 fungicidaacaricida 23 coadyuvanteinsecticidaacaricida 17 fitorregulador 15 defoliante 15 insetos 11 general lote 9 coberturas surcos malezas 9 contenido humedad suelo 9 fertilizante organico 7 coadyuvante 5 fugicida 3 plaga 3 fungida 3 fertilizante biologico 3 antidoto herbicida 3 Name: count, dtype: int64
Comparar bigrama con diccionario a partir de la distancia euclidea y similaridad del coseno¶
# euclidean distances
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import euclidean_distances
# Top 1000 bigramas
top_bigrams = (bigram_data.sort_values(by='Frequency', ascending=False)
.head(1000)['Bigram1'] + ' ' + bigram_data.sort_values(by='Frequency', ascending=False)
.head(1000)['Bigram2']).tolist()
# Combinamos texto
all_text = top_bigrams + df_combined['phrase'].tolist()
# Vectorizamos texto
vectorizer = TfidfVectorizer().fit(all_text)
bigram_vectors = vectorizer.transform(top_bigrams)
phrase_vectors = vectorizer.transform(df_combined['phrase'])
# Calculamos distancia
distance_matrix = euclidean_distances(bigram_vectors, phrase_vectors)
# Asignamos tags en base a las distanicas
tags = []
scores = []
phrases = []
for i, bigram in enumerate(top_bigrams):
# Obtenemos indice de la menor distancia
most_similar_index = distance_matrix[i].argmin()
# Obtenemos el tag asociado al indice
phrase = df_combined.iloc[most_similar_index]['phrase']
tag = df_combined.iloc[most_similar_index]['tag']
score = distance_matrix[i][most_similar_index]
tags.append(tag)
scores.append(score)
phrases.append(phrase)
# Creamos dataframe con los resultados
results_df_eucl_dist = pd.DataFrame({
'Bigram': top_bigrams,
'Tag': tags,
'Phrase': phrases,
'Score': scores
})
# Display the DataFrame
results_df_eucl_dist.sort_values(by='Score').head(100)
| Bigram | Tag | Phrase | Score | |
|---|---|---|---|---|
| 103 | stand plantas | stand plantas | stand plantas | 0.000000 |
| 394 | m l | analisis suelo | 0.000000 | |
| 48 | lote general | general lote | general lote | 0.000000 |
| 242 | mancha marron | enfermedades | mancha marron | 0.000000 |
| 648 | dual gold | herbicida | dual gold | 0.000000 |
| ... | ... | ... | ... | ... |
| 473 | tratamiento malezas | malezas | malezas | 0.863893 |
| 239 | nacimientos malezas | malezas | malezas | 0.863893 |
| 346 | control colorado | calidad siembra | control siembra | 0.870348 |
| 345 | con control | calidad siembra | control siembra | 0.872601 |
| 804 | amarilla con | enfermedades | mancha amarilla | 0.878307 |
100 rows × 4 columns
# similaridad coseno
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# Top 1000 bigramas
top_bigrams = (bigram_data.sort_values(by='Frequency', ascending=False)
.head(1000)['Bigram1'] + ' ' + bigram_data.sort_values(by='Frequency', ascending=False)
.head(1000)['Bigram2']).tolist()
# Combinamos texto
all_text = top_bigrams + df_combined['phrase'].tolist()
# Vectorizamos texto
vectorizer = TfidfVectorizer().fit(all_text)
bigram_vectors = vectorizer.transform(top_bigrams)
phrase_vectors = vectorizer.transform(df_combined['phrase'])
# Calculamos similaridad coseno
distance_matrix = cosine_similarity(bigram_vectors, phrase_vectors)
# Asignamos tags en base a las distanicas
tags = []
scores = []
phrases = []
for i, bigram in enumerate(top_bigrams):
# Obtenemos indice de la menor distancia
most_similar_index = distance_matrix[i].argmax()
# Obtenemos el tag asociado al indice
phrase = df_combined.iloc[most_similar_index]['phrase']
tag = df_combined.iloc[most_similar_index]['tag']
score = distance_matrix[i][most_similar_index]
tags.append(tag)
scores.append(score)
phrases.append(phrase)
# Creamos dataframe con los resultados
results_df_coseno_sim = pd.DataFrame({
'Bigram': top_bigrams,
'Tag': tags,
'Phrase': phrases,
'Score': scores
})
# Display the DataFrame
results_df_coseno_sim.sort_values(by='Score', ascending=False).head(100)
| Bigram | Tag | Phrase | Score | |
|---|---|---|---|---|
| 242 | mancha marron | enfermedades | mancha marron | 1.000000 |
| 944 | escoba dura | malezas | escoba dura | 1.000000 |
| 6 | maiz guacho | malezas | maiz guacho | 1.000000 |
| 289 | pasto bandera | malezas | pasto bandera | 1.000000 |
| 123 | ortiga mansa | malezas | ortiga mansa | 1.000000 |
| ... | ... | ... | ... | ... |
| 804 | amarilla con | enfermedades | mancha amarilla | 0.614289 |
| 899 | control lote | calidad siembra | control siembra | 0.612826 |
| 450 | malezas hoja | malezas | malezas | 0.612707 |
| 528 | siembra maiz | malezas | maiz guacho | 0.610937 |
| 672 | viendo cosecha | cosecha | cosecha | 0.609165 |
100 rows × 4 columns
Comparar bigrama con diccionario a partir de la distancia de Levenshtein¶
#Distancia de Levenshtein
import pandas as pd
import numpy as np
from Levenshtein import distance as levenshtein_distance
# Armamos lista de bigramas - TOP BIGRAMAS?
bigrams_list = (bigram_data.sort_values(by='Frequency', ascending=False)['Bigram1'] + ' ' + bigram_data.sort_values(by='Frequency', ascending=False)['Bigram2']).tolist()
# Definimos función para calcular la distancia de Levenshtein entre dos listas de strings
def levenshtein_distances(list1, list2):
distances = np.zeros((len(list1), len(list2)))
for i, s1 in enumerate(list1):
for j, s2 in enumerate(list2):
distances[i, j] = levenshtein_distance(s1, s2) / len(s1)
return distances
# Calculamos distancias de Levenshtein
distance_matrix = levenshtein_distances(bigrams_list, df_combined['phrase'])
# Asignamos el 'tag' del diccionario a partir de 'phrase'
tags = []
scores = []
phrases = []
for i, bigram in enumerate(bigrams_list):
# Index 'phrase' con menor distancia
most_similar_index = distance_matrix[i].argmin()
# Obtener el 'tag' a partir del index y la 'distancia'
phrase = df_combined.iloc[most_similar_index]['phrase']
tag = df_combined.iloc[most_similar_index]['tag']
score = distance_matrix[i][most_similar_index]
tags.append(tag)
scores.append(score)
phrases.append(phrase)
# Guardamos los resultados en un dataframe
results_df = pd.DataFrame({
'Bigram': bigrams_list,
'Tag': tags,
'Phrase': phrases,
'Score': scores
})
# Ver resultados
results_df.sort_values(by='Score').head(100)
| Bigram | Tag | Phrase | Score | |
|---|---|---|---|---|
| 54862 | oruga desgranadora | plagas | oruga desgranadora | 0.000000 |
| 2264 | zapallito amargo | malezas | zapallito amargo | 0.000000 |
| 11054 | analisis suelo | analisis suelo | analisis suelo | 0.000000 |
| 23727 | bowlesia incana | malezas | bowlesia incana | 0.000000 |
| 944 | escoba dura | malezas | escoba dura | 0.000000 |
| ... | ... | ... | ... | ... |
| 15657 | yuyo colorafo | malezas | yuyo colorado | 0.076923 |
| 9023 | yuyi colorado | malezas | yuyo colorado | 0.076923 |
| 46453 | general lotes | general lote | general lote | 0.076923 |
| 38651 | isoca medidor | plagas | isoca medidora | 0.076923 |
| 40947 | isocas espiga | plagas | isoca espiga | 0.076923 |
100 rows × 4 columns
results_df_2 = results_df.copy()
results_df_2["Score"] = round(results_df_2["Score"],2)
results_df_2.columns = ["Bigram","Phrase","Tag","Score"]
results_df_2.sort_values(by='Score').head(300).sample(15, random_state=15).sort_values("Score")
| Bigram | Phrase | Tag | Score | |
|---|---|---|---|---|
| 38007 | karate zeon | insecticida | karate zeon | 0.00 |
| 49826 | royas anaranjada | enfermedades | roya anaranjada | 0.06 |
| 51207 | choris virgata | malezas | chloris virgata | 0.07 |
| 3374 | rama negras | malezas | rama negra | 0.09 |
| 19489 | mami guacho | malezas | mani guacho | 0.09 |
| 15905 | rama negrs | malezas | rama negra | 0.10 |
| 10430 | lecheron chamico | malezas | lecheron chico | 0.12 |
| 13864 | bolsita pastor | malezas | bolsa pastor | 0.14 |
| 43126 | pastoreo colorado | malezas | pasto colorado | 0.18 |
| 923 | maiz wacho | malezas | maiz guacho | 0.20 |
| 10644 | x borreria | malezas | borreria | 0.20 |
| 22206 | seca mani | malezas | sacha mani | 0.22 |
| 40135 | commelina morenita | malezas | commelina erecta | 0.22 |
| 52976 | dula gold | herbicida | dual gold | 0.22 |
| 24885 | enfermedad ve | enfermedades | enfermedades | 0.23 |
# distribución Score
results_df["Score"].describe()
count 59552.000000 mean 0.547778 std 0.087395 min 0.000000 25% 0.500000 50% 0.562500 75% 0.600000 max 1.000000 Name: Score, dtype: float64
results_df["Score"].hist()
<Axes: >
# filtrando scores menores a 0.4
score_max = 0.42
results_df_filtrado = results_df[results_df["Score"]<score_max].reset_index(drop=True)
results_df_filtrado.sort_values(by='Score')
| Bigram | Tag | Phrase | Score | |
|---|---|---|---|---|
| 0 | rama negra | malezas | rama negra | 0.000000 |
| 133 | escoba dura | malezas | escoba dura | 0.000000 |
| 4442 | sonchus oleracea | malezas | sonchus oleracea | 0.000000 |
| 873 | gusano cogollero | plagas | gusano cogollero | 0.000000 |
| 1001 | bolsa pastor | malezas | bolsa pastor | 0.000000 |
| ... | ... | ... | ... | ... |
| 1011 | cargo zazola | malezas | carbon panoja | 0.416667 |
| 4781 | norte espiga | enfermedades | carbon espiga | 0.416667 |
| 2259 | dicamba pano | herbicida | dicamba | 0.416667 |
| 989 | bueno planta | stand plantas | stand plantas | 0.416667 |
| 716 | cosecha soja | cosecha | cosecha | 0.416667 |
5194 rows × 4 columns
# Guardamos resultado Levenshtein para etiquetar
if SAVE_NOTEBOOK_RESULTS:
results_df_filtrado.to_csv("Workflow_output/results_df_filtrado.csv", index=False)
# Guardamos resultado Levenshtein sin punto de corte (sin filtrar)
if SAVE_NOTEBOOK_RESULTS:
results_df.to_csv("Workflow_output/results_df.csv", index=False)
Asignar 'tag' sobre el dataset final¶
# definimos función para matchear los tags de los bigramas con los comentarios
def find_all_tags(comment, phrase_to_tag):
return [tag for phrase, tag in phrase_to_tag.items() if phrase in comment.lower()]
# aplicamos
# crear un diccionario de bigramas y phrases
phrase_to_tag = dict(zip(results_df_filtrado['Bigram'].str.lower(), results_df_filtrado['Phrase']))
annotations['comment_phrases'] = annotations['comments_clean_2'].apply(lambda comment: find_all_tags(comment, phrase_to_tag))
# crear un diccionario de bigramas y scores
phrase_to_tag = dict(zip(results_df_filtrado['Bigram'].str.lower(), results_df_filtrado['Score']))
annotations['comment_scores'] = annotations['comments_clean_2'].apply(lambda comment: find_all_tags(comment, phrase_to_tag))
# crear un diccionario de bigramas y tags
phrase_to_tag = dict(zip(results_df_filtrado['Bigram'].str.lower(), results_df_filtrado['Tag']))
annotations['comment_tags'] = annotations['comments_clean_2'].apply(lambda comment: find_all_tags(comment, phrase_to_tag))
# Función para obtener el tag o score con el mayor valor (o 'sin etiquetar' si la lista está vacía)
def get_max_value(row, value_type='tag'):
if row['comment_scores']: # Verificamos si la lista no está vacía
max_score_index = row['comment_scores'].index(min(row['comment_scores'])) # O min si quieres el valor más bajo
return row['comment_tags'][max_score_index] if value_type == 'tag' else row['comment_scores'][max_score_index]
else:
return 'sin etiquetar' # Devuelve 'sin etiquetar' si la lista de scores está vacía
# Aplicamos la función al DataFrame para tags
annotations['best_tag'] = annotations.apply(lambda row: get_max_value(row, 'tag'), axis=1)
# Aplicamos la función al DataFrame para scores
annotations['best_score'] = annotations.apply(lambda row: get_max_value(row, 'score'), axis=1)
# distribución de los tags imputados a partir de los comentarios
annotations['best_tag'].value_counts(dropna=False, normalize=True).head(6).round(2)
best_tag sin etiquetar 0.57 malezas 0.22 general lote 0.05 plagas 0.04 enfermedades 0.03 fungicida 0.02 Name: proportion, dtype: float64
annotations#[annotations['best_tag']=="plagas"]
| annotation_id | company_id | season_id | property_id | area_id | comments | scouter_id | annotation_date | annotation_tags | annotation_longitude | annotation_latitude | comments_clean | len_comments_clean | comments_clean_2 | len_comments_clean_2 | comment_phrases | comment_scores | comment_tags | best_tag | best_score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 01ff3c51-d6c2-4b9a-9876-409e75a824d5 | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | 289c09d7-ce26-4fa9-bf9a-b7cab391898f | bd3e8b1b-6914-4e22-9e63-1c0c66ccb148 | 91420df8-61ba-11ed-9c62-e1a227e78cde | Ir hablado con desyuyadores | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-07 13:31:09.234 | NaN | -62.578112 | -33.103599 | ir hablado con desyuyadores | 27 | ir hablado con desyuyadores | 27 | [] | [] | [] | sin etiquetar | sin etiquetar |
| 1 | 404e3e04-b713-493e-830a-56570f997a14 | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | 036afeae-1c42-4c93-9a06-2f20977b89ac | Maiz recien sembrado . Todo ok | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 10:30:55.251 | NaN | -62.809891 | -33.741697 | maiz recien sembrado todo ok | 28 | maiz recien sembrado ok | 23 | [] | [] | [] | sin etiquetar | sin etiquetar |
| 2 | 60f06b01-c926-42b3-b79d-5b4b61e2e6fb | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | b24c5fd5-87b2-40dd-97cc-ca4edff7a625 | Comienzo siembra maiz tardio. Excelente barbecho | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 10:30:05.531 | NaN | -62.809891 | -33.741697 | comienzo siembra maiz tardio excelente barbecho | 47 | comienzo siembra maiz tardio excelente barbecho | 47 | [calidad siembra] | [0.375] | [calidad siembra] | calidad siembra | 0.375 |
| 3 | b53c4cac-1a21-45fe-be7c-235b08d704c9 | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | 1af5d0f7-7a22-4aef-8c10-c65c12708ab3 | Hacer preemergente con glifo y acuron | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 12:18:16.170 | NaN | -62.770486 | -33.655338 | hacer preemergente con glifo y acuron | 37 | hacer preemergente con glifo acuron | 35 | [] | [] | [] | sin etiquetar | sin etiquetar |
| 4 | 6bbdc37c-4ca4-4082-a443-aedd206cf24e | 0afb4f42-d63b-45ee-bbf8-df16f94529f7 | dcbb35dc-a961-4654-82f4-cb84e8e95329 | 07dc5555-019b-44c3-8045-ce127632a1ae | 9d6caf86-eeca-4124-a2fb-42394a148771 | Hacer preemergente con acuron y heat | ac8b985a-2f62-4dab-9a57-bad510df7204 | 2022-12-14 12:17:43.856 | NaN | -62.770486 | -33.655338 | hacer preemergente con acuron y heat | 36 | hacer preemergente con acuron heat | 34 | [] | [] | [] | sin etiquetar | sin etiquetar |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 19728 | 0572c958-07e6-427b-9754-098a14a148c9 | 33f2dcae-049a-4b93-83f3-a8014ea7f0a0 | c8a8c727-206e-46b6-a206-114aa68e824d | 0f806757-e61d-4f67-81d0-0e71d97cb668 | 08860540-d869-11ed-98c6-150405b698ac | lote muy limpio y muy bien planteado sobre soj... | 2302d65d-52d4-4633-82b1-60523803d468 | 2024-08-27 18:15:41.000 | NaN | -59.081522 | -37.594106 | lote muy limpio y muy bien planteado sobre soj... | 52 | lote limpio bien planteado soja | 31 | [] | [] | [] | sin etiquetar | sin etiquetar |
| 19729 | ce32d686-b5ad-40ea-9d8e-bf3f6666db07 | e88eecd6-d4f5-4b65-b9f2-87fc4b8de35c | 7748349f-546c-4347-a194-82ec1aaa7173 | 42c22adc-1fa6-4c56-a594-f41120f6f77f | 33335951-fa9f-4aad-9cd4-abab2869f225 | Muy lindo esta el trigo.\r\nPulgon muy poco se... | 03401ba8-df2d-43f1-a386-06f7a28e0ca3 | 2024-08-30 14:07:23.000 | tags_general_disease,tags_general_pest | -63.748140 | -30.023821 | muy lindo esta el trigo pulgon muy poco se ve ... | 75 | lindo trigo pulgon ve roya ve llegando hb | 41 | [] | [] | [] | sin etiquetar | sin etiquetar |
| 19730 | 4e72bf13-5f07-431f-9188-1526dbc76901 | 6db7039e-7ffe-4f7b-a395-2d599879680b | 261b3a42-5912-46d9-ba0e-48cd8a2fa7e3 | e6b344e5-63d1-4ee7-b970-e7477c2342f6 | c1134ad0-e89f-4e91-96c3-28bd326d0086 | rastrojo limpio malezas | 7c522b74-8858-4bae-9e34-aa715d329f32 | 2024-08-29 19:19:44.000 | NaN | -60.466058 | -34.635506 | rastrojo limpio malezas | 23 | rastrojo limpio malezas | 23 | [] | [] | [] | sin etiquetar | sin etiquetar |
| 19731 | 604d7b7e-92c4-4307-89a8-6e91da2be83c | 6db7039e-7ffe-4f7b-a395-2d599879680b | 261b3a42-5912-46d9-ba0e-48cd8a2fa7e3 | e6b344e5-63d1-4ee7-b970-e7477c2342f6 | d2a4cc82-d4a5-4a1c-bea3-0a1094555766 | efecto herbicida en malezas | 7c522b74-8858-4bae-9e34-aa715d329f32 | 2024-08-29 19:20:41.000 | NaN | -60.466058 | -34.635506 | efecto herbicida en malezas | 27 | efecto herbicida malezas | 24 | [antidoto herbicida] | [0.375] | [antidoto herbicida] | antidoto herbicida | 0.375 |
| 19732 | 3c3ef340-7c87-43e4-b76b-814ad1143ea0 | 6db7039e-7ffe-4f7b-a395-2d599879680b | 261b3a42-5912-46d9-ba0e-48cd8a2fa7e3 | e6b344e5-63d1-4ee7-b970-e7477c2342f6 | 1349377d-0e73-42f7-a708-03daf9d4f735 | efecto herbicida en malezas | 7c522b74-8858-4bae-9e34-aa715d329f32 | 2024-08-29 19:20:15.000 | NaN | -60.466058 | -34.635506 | efecto herbicida en malezas | 27 | efecto herbicida malezas | 24 | [antidoto herbicida] | [0.375] | [antidoto herbicida] | antidoto herbicida | 0.375 |
19733 rows × 20 columns
# Guardamos base para modelo
if SAVE_NOTEBOOK_RESULTS:
annotations.to_csv(file_with_tags, index=False)
Modelos de Clasificación¶
annotations = pd.read_csv(file_with_tags)
# preparar base
condicion = (annotations["best_tag"].isin(["malezas","enfermedades","plagas","fungicida","general lote"]))
campos_interes = ["comments_clean_2", "best_tag"]#, "comments_clean","comments_clean_2","best_score"]
df_model = annotations[condicion][campos_interes].reset_index(drop=True)
df_model
| comments_clean_2 | best_tag | |
|---|---|---|
| 0 | excelente control malezas | malezas |
| 1 | ojo seguir tema bolillera ipro | plagas |
| 2 | ruedas cebollin con control parcial monitoreo ... | malezas |
| 3 | maiz guacho naciendo sorgo halepo baja intensidad | malezas |
| 4 | no ven nuevas infecciones enfermedades fungica... | plagas |
| ... | ... | ... |
| 5908 | cerraja mostacilla maiz guacho cebadilla basta... | malezas |
| 5909 | cardos nabos capiqui rama negra ir con metribu... | malezas |
| 5910 | manchones chicos avena guacha | malezas |
| 5911 | manchones avena negra lamentablemente sector p... | malezas |
| 5912 | avena negra ir con axial junto con poste | malezas |
5913 rows × 2 columns
import nltk
from nltk.classify import NaiveBayesClassifier, MaxentClassifier, SklearnClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report, accuracy_score
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
X = df_model['comments_clean_2']
y = df_model['best_tag']
# Dividir en conjunto de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Vectorización de texto con TF-IDF
vectorizer = TfidfVectorizer(max_features=5000)
X_train_tfidf = vectorizer.fit_transform(X_train).toarray()
X_test_tfidf = vectorizer.transform(X_test).toarray()
# Crear pares de características y etiquetas para nltk
train_data = [(dict(enumerate(x)), label) for x, label in zip(X_train_tfidf, y_train)]
test_data = [(dict(enumerate(x)), label) for x, label in zip(X_test_tfidf, y_test)]
# Naive Bayes Classifier
print("Naive Bayes Classifier")
nb_classifier = NaiveBayesClassifier.train(train_data)
nb_accuracy = nltk.classify.accuracy(nb_classifier, test_data)
print("Naive Bayes Accuracy:", nb_accuracy)
# Maxent Classifier (requiere el algoritmo GIS)
print("Maxent Classifier")
maxent_classifier = MaxentClassifier.train(train_data, algorithm='gis', max_iter=4)#, trace=0, max_iter=3, min_lldelta=0.5)#, algorithm='gis', max_iter=10)
maxent_accuracy = nltk.classify.accuracy(maxent_classifier, test_data)
print("Maxent Classifier Accuracy:", maxent_accuracy)
# SklearnClassifier con Logistic Regression
#print("SklearnClassifier")
sklearn_classifier = SklearnClassifier(MultinomialNB()).train(train_data)
sklearn_accuracy = nltk.classify.accuracy(sklearn_classifier, test_data)
print("MultinomialNB (SklearnClassifier) Accuracy:", sklearn_accuracy)
Naive Bayes Classifier
Naive Bayes Accuracy: 0.6534234995773457
Maxent Classifier
==> Training (4 iterations)
Iteration Log Likelihood Accuracy
---------------------------------------
1 -1.60944 0.124
2 -0.83047 0.630
3 -0.82752 0.630
Final -0.82538 0.630
Maxent Classifier Accuracy: 0.6348267117497887
MultinomialNB (SklearnClassifier) Accuracy: 0.7743026204564666
# Generar reporte detallado
nb_classifier_model = nb_classifier # puedes elegir el mejor clasificador
y_pred_nb_classifier = [nb_classifier_model.classify(dict(enumerate(vectorizer.transform([comment]).toarray()[0]))) for comment in X_test]
maxent_classifier_model = maxent_classifier # puedes elegir el mejor clasificador
y_pred_maxent_classifier = [maxent_classifier_model.classify(dict(enumerate(vectorizer.transform([comment]).toarray()[0]))) for comment in X_test]
sklearn_classifier_model = sklearn_classifier # puedes elegir el mejor clasificador
y_pred_sklearn_classifier = [sklearn_classifier_model.classify(dict(enumerate(vectorizer.transform([comment]).toarray()[0]))) for comment in X_test]
df_test = pd.DataFrame({"y_test":y_test,"y_pred_nb_classifier":y_pred_nb_classifier,"y_pred_maxent_classifier":y_pred_maxent_classifier, "y_pred_sklearn_classifier":y_pred_sklearn_classifier})
df_test
| y_test | y_pred_nb_classifier | y_pred_maxent_classifier | y_pred_sklearn_classifier | |
|---|---|---|---|---|
| 5579 | malezas | malezas | malezas | malezas |
| 3443 | malezas | malezas | malezas | malezas |
| 2351 | malezas | malezas | malezas | malezas |
| 4456 | enfermedades | malezas | malezas | general lote |
| 2742 | malezas | malezas | malezas | malezas |
| ... | ... | ... | ... | ... |
| 1869 | plagas | malezas | malezas | enfermedades |
| 2025 | enfermedades | malezas | malezas | malezas |
| 5788 | malezas | malezas | malezas | malezas |
| 653 | malezas | malezas | malezas | malezas |
| 429 | malezas | malezas | malezas | malezas |
1183 rows × 4 columns
print(df_test["y_test"].value_counts())
print(df_test["y_pred_nb_classifier"].value_counts())
print(df_test["y_pred_maxent_classifier"].value_counts())
print(df_test["y_pred_sklearn_classifier"].value_counts())
y_test malezas 751 plagas 151 enfermedades 119 general lote 111 fungicida 51 Name: count, dtype: int64 y_pred_nb_classifier malezas 1043 plagas 73 general lote 54 enfermedades 12 fungicida 1 Name: count, dtype: int64 y_pred_maxent_classifier malezas 1183 Name: count, dtype: int64 y_pred_sklearn_classifier malezas 963 plagas 94 general lote 63 enfermedades 54 fungicida 9 Name: count, dtype: int64
MaxentClassifier clasifica todos los comentarios como 'malezas'.
#df_test[df_test["y_pred_sklearn_classifier"]=="enfermedades"]
MaxentClassifier¶
print("\nClassification Report:\n", classification_report(df_test["y_test"], df_test["y_pred_maxent_classifier"]))
Classification Report:
precision recall f1-score support
enfermedades 0.00 0.00 0.00 119
fungicida 0.00 0.00 0.00 51
general lote 0.00 0.00 0.00 111
malezas 0.63 1.00 0.78 751
plagas 0.00 0.00 0.00 151
accuracy 0.63 1183
macro avg 0.13 0.20 0.16 1183
weighted avg 0.40 0.63 0.49 1183
NBClassifier¶
print("\nClassification Report:\n", classification_report(df_test["y_test"], df_test["y_pred_nb_classifier"]))
Classification Report:
precision recall f1-score support
enfermedades 0.83 0.08 0.15 119
fungicida 1.00 0.02 0.04 51
general lote 0.59 0.29 0.39 111
malezas 0.68 0.94 0.79 751
plagas 0.33 0.16 0.21 151
accuracy 0.65 1183
macro avg 0.69 0.30 0.32 1183
weighted avg 0.65 0.65 0.58 1183
Sklearn Classifier¶
print("\nClassification Report:\n", classification_report(df_test["y_test"], df_test["y_pred_sklearn_classifier"]))
Classification Report:
precision recall f1-score support
enfermedades 0.87 0.39 0.54 119
fungicida 1.00 0.18 0.30 51
general lote 0.75 0.42 0.54 111
malezas 0.77 0.99 0.86 751
plagas 0.77 0.48 0.59 151
accuracy 0.77 1183
macro avg 0.83 0.49 0.57 1183
weighted avg 0.79 0.77 0.74 1183
Validación¶
df_validation = pd.read_csv('../data/validation.csv', encoding='ISO-8859-1')
# limpieza
df_validation.loc[df_validation["specialist_tag"]=="maleza","specialist_tag"] = "malezas"
df_validation.loc[df_validation["specialist_tag"]=="plaga","specialist_tag"] = "plagas"
df_validation = df_validation[~df_validation["specialist_tag"].isna()].copy().reset_index(drop=True)
# filtramos etiquetas de interes
df_validation_filtrada = df_validation[df_validation["specialist_tag"].isin(['malezas','plagas','enfermedades','herbicida','fungicida','insecticida'])].copy().reset_index(drop=True)
df_validation_filtrada.head()
| annotation_id | company_id | season_id | property_id | area_id | comments | specialist_tag | scouter_id | annotation_date | annotation_tags | annotation_longitude | annotation_latitude | comments_clean_2 | comment_phrases | comment_scores | comment_tags | best_tag | best_score | y_pred_sklearn_classifier | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0c228426-d0a5-4c1c-b4d2-5729053d737e | 1e09a723-b78a-405a-ba7d-4bb85d41fe3b | 3aaf737d-241f-4376-ac44-317e455a65ca | 1d4db68f-61f3-49a8-b9c9-95818da5781b | 8bd5665c-a6e0-4eff-a07c-7e66e5947d07 | rodales de rizoctonia | enfermedades | b434f734-51cf-47f0-bcb1-15d1de934b87 | 8/15/2023 | tags_general_disease | -61.262533 | -38.824346 | rodales rizoctonia | [] | [] | [] | sin etiquetar | sin etiquetar | malezas |
| 1 | 6fafefea-5341-44ef-83ea-0c9ec4a39551 | 55ec9c39-a240-4db4-91f1-4d4cfa961250 | 3d7313f6-7753-40b8-b0d5-314700848c02 | a380fba2-6d6d-4ea8-b257-62a1b626452f | fc011c87-24f9-46d8-b54a-168b80818b11 | Trigo MS 119 y MS 221 por el momento se compor... | enfermedades | f6160633-bee5-4881-9038-1576bf3ed8e7 | 8/8/2023 | tags_general_disease | -61.963047 | -32.749646 | trigo ms ms momento comporta bien roya anaranjada | [roya anaranjada] | [0.0] | [enfermedades] | enfermedades | 0.0 | enfermedades |
| 2 | 47d21463-875a-4270-8a65-f908d5246518 | 2ac77d56-6fc5-4e28-be3f-9e3147fadcee | d0925a06-8b39-4ca1-9b5b-04a54e3f1676 | b769fa29-bfa2-4598-b184-53570f447e02 | dfe6e756-68df-40b8-aad6-4298e98274da | Roya Amarilla controlada | enfermedades | 36568ca0-67ca-49ac-a44b-084e52d2c584 | 8/28/2023 | tags_general_disease | -63.419535 | -31.155053 | roya amarilla controlada | [roya asiatica] | [0.38461538461538464] | [enfermedades] | enfermedades | 0.384615 | enfermedades |
| 3 | 82a64138-1be2-481b-a96f-aee4e4635c51 | e8b4d4a7-825e-49e4-94c5-5730fa5ad33e | db99aee4-7ae9-42d7-a4b5-31d2abc81e86 | 6cb5fc79-5d9b-485b-a6a1-b3f5721de67a | d39afe28-387a-411e-a3a6-9205177228a7 | Bastante roya amarilla en las hojas inferiores... | enfermedades | 54cd9c8c-0799-449c-9a22-d9f75c5dd173 | 9/5/2023 | tags_general_disease | -61.542116 | -38.725714 | bastante roya amarilla hojas inferiores vamo a... | [roya asiatica] | [0.38461538461538464] | [enfermedades] | enfermedades | 0.384615 | enfermedades |
| 4 | 84d6daf6-f51e-4444-bbb0-addc3b1fe07f | 0ba19ff1-3805-4f5b-81c1-fa46a617f6f9 | 8afa750f-5296-416a-a7e7-db691b0af69e | 2aca3eb4-1bd3-4d38-8912-9d6aba28a3e7 | fb41822c-be1c-48e2-89ea-abb993b2172c | estriado mas pustulas | enfermedades | f60a01a5-4145-4343-9d71-026617c8cb6a | 9/18/2023 | tags_general_disease | -61.292842 | -38.825694 | estriado pustulas | [] | [] | [] | sin etiquetar | sin etiquetar | enfermedades |
df_validation_filtrada.specialist_tag.value_counts()
specialist_tag malezas 437 enfermedades 55 plagas 51 herbicida 26 insecticida 2 Name: count, dtype: int64
# aplicamos modelo vs clasificador
# procesamiento
df_validation_filtrada['comments_clean_2'] = df_validation_filtrada['comments'].apply(limpiar_texto)
# aplicamos clasificador con diccionario
# crear un diccionario de bigramas y phrases
phrase_to_tag = dict(zip(results_df_filtrado['Bigram'].str.lower(), results_df_filtrado['Phrase']))
df_validation_filtrada['comment_phrases'] = df_validation_filtrada['comments_clean_2'].apply(lambda comment: find_all_tags(comment, phrase_to_tag))
# crear un diccionario de bigramas y scores
phrase_to_tag = dict(zip(results_df_filtrado['Bigram'].str.lower(), results_df_filtrado['Score']))
df_validation_filtrada['comment_scores'] = df_validation_filtrada['comments_clean_2'].apply(lambda comment: find_all_tags(comment, phrase_to_tag))
# crear un diccionario de bigramas y tags
phrase_to_tag = dict(zip(results_df_filtrado['Bigram'].str.lower(), results_df_filtrado['Tag']))
df_validation_filtrada['comment_tags'] = df_validation_filtrada['comments_clean_2'].apply(lambda comment: find_all_tags(comment, phrase_to_tag))
# Aplicamos la función al DataFrame para tags
df_validation_filtrada['best_tag'] = df_validation_filtrada.apply(lambda row: get_max_value(row, 'tag'), axis=1)
# Aplicamos la función al DataFrame para scores
df_validation_filtrada['best_score'] = df_validation_filtrada.apply(lambda row: get_max_value(row, 'score'), axis=1)
# aplicamos modelo
y_pred_sklearn_classifier = [sklearn_classifier_model.classify(dict(enumerate(vectorizer.transform([comment]).toarray()[0]))) for comment in df_validation_filtrada['comments_clean_2']]
df_validation_filtrada["y_pred_sklearn_classifier"] = y_pred_sklearn_classifier
print("\nClassification Report:\n", classification_report(df_validation_filtrada["specialist_tag"], df_validation_filtrada["best_tag"]))
Classification Report:
precision recall f1-score support
calidad siembra 0.00 0.00 0.00 0
enfermedades 0.88 0.25 0.39 55
fungicida 0.00 0.00 0.00 0
general lote 0.00 0.00 0.00 0
herbicida 1.00 0.35 0.51 26
insecticida 0.00 0.00 0.00 2
malas hierbas 0.00 0.00 0.00 0
malezas 0.99 0.42 0.59 437
plagas 0.87 0.25 0.39 51
plantas daninhas 0.00 0.00 0.00 0
sin etiquetar 0.00 0.00 0.00 0
stand plantas 0.00 0.00 0.00 0
unkrauter 0.00 0.00 0.00 0
accuracy 0.38 571
macro avg 0.29 0.10 0.15 571
weighted avg 0.96 0.38 0.55 571
Clasificación con diccionario y distancia de Levenshtein - F1 Score:
- enfermedades: 0.39
- herbicida: 0.51
- insecticida: 0.00
- malezas: 0.59
- plagas: 0.39
print("\nClassification Report:\n", classification_report(df_validation_filtrada["specialist_tag"], df_validation_filtrada["y_pred_sklearn_classifier"]))
Classification Report:
precision recall f1-score support
enfermedades 1.00 0.62 0.76 55
herbicida 0.00 0.00 0.00 26
insecticida 0.00 0.00 0.00 2
malezas 0.85 1.00 0.92 437
plagas 0.95 0.37 0.54 51
accuracy 0.86 571
macro avg 0.56 0.40 0.44 571
weighted avg 0.83 0.86 0.82 571
Sklearn Classifier - F1 Score:
- enfermedades: 0.76
- herbicida: 0.00
- insecticida: 0.00
- malezas: 0.92
- plagas: 0.54